msg_tool\scripts\hexen_haus\archive/
arcc.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::decode_to_string;
6use anyhow::Result;
7use std::io::{Read, Seek, SeekFrom};
8use std::sync::{Arc, Mutex};
9
10#[derive(Debug)]
11pub struct HexenHausArccArchiveBuilder;
13
14impl HexenHausArccArchiveBuilder {
15 pub const fn new() -> Self {
17 HexenHausArccArchiveBuilder
18 }
19}
20
21impl ScriptBuilder for HexenHausArccArchiveBuilder {
22 fn default_encoding(&self) -> Encoding {
23 Encoding::Cp932
24 }
25
26 fn default_archive_encoding(&self) -> Option<Encoding> {
27 Some(Encoding::Cp932)
28 }
29
30 fn build_script(
31 &self,
32 buf: Vec<u8>,
33 _filename: &str,
34 _encoding: Encoding,
35 archive_encoding: Encoding,
36 config: &ExtraConfig,
37 _archive: Option<&Box<dyn Script>>,
38 ) -> Result<Box<dyn Script>> {
39 Ok(Box::new(HexenHausArccArchive::new(
40 MemReader::new(buf),
41 archive_encoding,
42 config,
43 )?))
44 }
45
46 fn build_script_from_file(
47 &self,
48 filename: &str,
49 _encoding: Encoding,
50 archive_encoding: Encoding,
51 config: &ExtraConfig,
52 _archive: Option<&Box<dyn Script>>,
53 ) -> Result<Box<dyn Script>> {
54 if filename == "-" {
55 let data = crate::utils::files::read_file(filename)?;
56 return Ok(Box::new(HexenHausArccArchive::new(
57 MemReader::new(data),
58 archive_encoding,
59 config,
60 )?));
61 }
62 let file = std::fs::File::open(filename)?;
63 let reader = std::io::BufReader::new(file);
64 Ok(Box::new(HexenHausArccArchive::new(
65 reader,
66 archive_encoding,
67 config,
68 )?))
69 }
70
71 fn build_script_from_reader(
72 &self,
73 reader: Box<dyn ReadSeek>,
74 _filename: &str,
75 _encoding: Encoding,
76 archive_encoding: Encoding,
77 config: &ExtraConfig,
78 _archive: Option<&Box<dyn Script>>,
79 ) -> Result<Box<dyn Script>> {
80 Ok(Box::new(HexenHausArccArchive::new(
81 reader,
82 archive_encoding,
83 config,
84 )?))
85 }
86
87 fn extensions(&self) -> &'static [&'static str] {
88 &["arc"]
89 }
90
91 fn script_type(&self) -> &'static ScriptType {
92 &ScriptType::HexenHausArcc
93 }
94
95 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
96 if buf_len >= 4 && buf.starts_with(b"ARCC") {
97 Some(10)
98 } else {
99 None
100 }
101 }
102
103 fn is_archive(&self) -> bool {
104 true
105 }
106}
107
108#[derive(Debug, Clone)]
109struct HexenHausArccEntry {
110 name: String,
111 offset: u64,
112 size: u32,
113}
114
115#[derive(Debug)]
116pub struct HexenHausArccArchive<T: Read + Seek + std::fmt::Debug> {
118 reader: Arc<Mutex<T>>,
119 entries: Vec<HexenHausArccEntry>,
120}
121
122impl<T: Read + Seek + std::fmt::Debug> HexenHausArccArchive<T> {
123 pub fn new(mut reader: T, archive_encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
125 reader.seek(SeekFrom::Start(0))?;
126 let mut signature = [0u8; 4];
127 reader.read_exact(&mut signature)?;
128 if signature != *b"ARCC" {
129 return Err(anyhow::anyhow!("Invalid HexenHaus ARCC signature"));
130 }
131 reader.seek(SeekFrom::Start(0))?;
132 let reader = Arc::new(Mutex::new(reader));
133
134 let file_count = reader.cpeek_u32_at(0x14)?;
135 let entry_count = file_count as usize;
136
137 let mut index_offset = 0x2a_u64;
138 let mut tag = [0u8; 4];
139 reader.cpeek_exact_at(index_offset, &mut tag)?;
140 if &tag != b"NAME" {
141 return Err(anyhow::anyhow!("Missing NAME section in ARCC archive"));
142 }
143 let addr_offset = reader.cpeek_u64_at(index_offset + 4)?;
144 index_offset += 0x0e;
145
146 reader.cpeek_exact_at(index_offset, &mut tag)?;
147 if &tag != b"NIDX" {
148 return Err(anyhow::anyhow!("Missing NIDX section in ARCC archive"));
149 }
150 index_offset += 4;
151 for _ in 0..entry_count {
152 let _ = reader.cpeek_u32_at(index_offset + 2)?;
153 index_offset += 8;
154 }
155
156 reader.cpeek_exact_at(index_offset, &mut tag)?;
157 if &tag != b"EIDX" {
158 return Err(anyhow::anyhow!("Missing EIDX section in ARCC archive"));
159 }
160 index_offset += 4 + 8 * file_count as u64;
161
162 reader.cpeek_exact_at(index_offset, &mut tag)?;
163 if &tag != b"CINF" {
164 return Err(anyhow::anyhow!("Missing CINF section in ARCC archive"));
165 }
166 index_offset += 4;
167
168 let mut entries = Vec::with_capacity(entry_count);
169 for _ in 0..entry_count {
170 index_offset += 6;
171 let name_len = reader.cpeek_u16_at(index_offset)? as usize;
172 let mut name_buf = vec![0u8; name_len];
173 if name_len > 0 {
174 reader.cpeek_exact_at(index_offset + 4, &mut name_buf)?;
175 decrypt_name(&mut name_buf);
176 }
177 index_offset += 6 + name_len as u64;
178 let name = decode_to_string(archive_encoding, &name_buf, true)?;
179 entries.push(HexenHausArccEntry {
180 name,
181 offset: 0,
182 size: 0,
183 });
184 }
185
186 let mut addr_offset = addr_offset;
187 reader.cpeek_exact_at(addr_offset, &mut tag)?;
188 if &tag != b"ADDR" {
189 return Err(anyhow::anyhow!("Missing ADDR section in ARCC archive"));
190 }
191 addr_offset += 4;
192 for entry in &mut entries {
193 entry.offset = reader.cpeek_u64_at(addr_offset + 2)?;
194 addr_offset += 12;
195 }
196
197 for entry in &mut entries {
198 if reader.cpeek_and_equal_at(entry.offset, b"FILE").is_err() {
199 continue;
200 }
201 entry.size = reader.cpeek_u32_at(entry.offset + 0x18)?;
202 entry.offset += 0x22;
203 }
204
205 entries.retain(|entry| entry.size > 0);
206 if entries.is_empty() {
207 return Err(anyhow::anyhow!("ARCC archive contains no files"));
208 }
209
210 Ok(HexenHausArccArchive { reader, entries })
211 }
212}
213
214impl<T: Read + Seek + std::fmt::Debug + std::any::Any> Script for HexenHausArccArchive<T> {
215 fn default_output_script_type(&self) -> OutputScriptType {
216 OutputScriptType::Json
217 }
218
219 fn default_format_type(&self) -> FormatOptions {
220 FormatOptions::None
221 }
222
223 fn is_archive(&self) -> bool {
224 true
225 }
226
227 fn iter_archive_filename<'a>(
228 &'a self,
229 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
230 Ok(Box::new(
231 self.entries.iter().map(|entry| Ok(entry.name.clone())),
232 ))
233 }
234
235 fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
236 Ok(Box::new(self.entries.iter().map(|entry| Ok(entry.offset))))
237 }
238
239 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + 'a>> {
240 if index >= self.entries.len() {
241 return Err(anyhow::anyhow!(
242 "Index out of bounds: {} (total files: {})",
243 index,
244 self.entries.len()
245 ));
246 }
247 let entry = &self.entries[index];
248 let header = self
249 .reader
250 .cpeek_at_vec(entry.offset, (entry.size as usize).min(16))?;
251 Ok(Box::new(Entry {
252 reader: self.reader.clone(),
253 header: entry.clone(),
254 pos: 0,
255 typ: super::detect_script_type(&entry.name, &header),
256 }))
257 }
258}
259
260struct Entry<T: Read + Seek> {
261 header: HexenHausArccEntry,
262 reader: Arc<Mutex<T>>,
263 pos: u64,
264 typ: Option<ScriptType>,
265}
266
267impl<T: Read + Seek> ArchiveContent for Entry<T> {
268 fn name(&self) -> &str {
269 &self.header.name
270 }
271
272 fn script_type(&self) -> Option<&ScriptType> {
273 self.typ.as_ref()
274 }
275}
276
277impl<T: Read + Seek> Read for Entry<T> {
278 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
279 let mut reader = self.reader.lock().map_err(|e| {
280 std::io::Error::new(
281 std::io::ErrorKind::Other,
282 format!("Failed to lock mutex: {}", e),
283 )
284 })?;
285 reader.seek(SeekFrom::Start(self.header.offset + self.pos))?;
286 let bytes_read = buf.len().min(self.header.size as usize - self.pos as usize);
287 if bytes_read == 0 {
288 return Ok(0);
289 }
290 let bytes_read = reader.read(&mut buf[..bytes_read])?;
291 self.pos += bytes_read as u64;
292 Ok(bytes_read)
293 }
294}
295
296impl<T: Read + Seek> Seek for Entry<T> {
297 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
298 let new_pos = match pos {
299 SeekFrom::Start(offset) => offset as u64,
300 SeekFrom::End(offset) => {
301 if offset < 0 {
302 if (-offset) as u64 > self.header.size as u64 {
303 return Err(std::io::Error::new(
304 std::io::ErrorKind::InvalidInput,
305 "Seek from end exceeds file length",
306 ));
307 }
308 self.header.size as u64 - (-offset) as u64
309 } else {
310 self.header.size as u64 + offset as u64
311 }
312 }
313 SeekFrom::Current(offset) => {
314 if offset < 0 {
315 if (-offset) as u64 > self.pos {
316 return Err(std::io::Error::new(
317 std::io::ErrorKind::InvalidInput,
318 "Seek from current exceeds current position",
319 ));
320 }
321 self.pos.saturating_sub((-offset) as u64)
322 } else {
323 self.pos + offset as u64
324 }
325 }
326 };
327 self.pos = new_pos;
328 Ok(self.pos)
329 }
330
331 fn stream_position(&mut self) -> std::io::Result<u64> {
332 Ok(self.pos)
333 }
334}
335
336fn decrypt_name(buf: &mut [u8]) {
337 for byte in buf.iter_mut() {
338 *byte ^= 0x69;
339 }
340}